#!/usr/bin/python3
#----------------------------------------------------------------------------------# 
#                         CVE-2005-1794 Check                                      #
#               http://www.oxid.it/ca_um/topics/apr-rdp.htm                        #
# Simple script to demonstrate a host is vulnerable to CVE-2005-1794.              #
# Tests for Windows remote desktop (RDP) hard coded RSA private key.               #       
# This uses code from https://github.com/SySS-Research/Seth to parse a certifcate  #
# from a host and extract the signature of the certificate, it then uses the known #
# hardcoded RSA key to sign the certificate and compares the two Signatures        #
# if they match, the host is vulnerable.                                           #
# Author: David Yesland @daveysec, Rhino Security Labs                             #
#----------------------------------------------------------------------------------#


import socket
import hashlib
import struct
import binascii
import re
import sys
import textwrap

# Taken from https://github.com/SySS-Research/Seth/blob/master/seth/consts.py
TERM_PRIV_KEY = { # little endian, from [MS-RDPBCGR].pdf
    "n": [ 0x3d, 0x3a, 0x5e, 0xbd, 0x72, 0x43, 0x3e, 0xc9, 0x4d, 0xbb, 0xc1,
          0x1e, 0x4a, 0xba, 0x5f, 0xcb, 0x3e, 0x88, 0x20, 0x87, 0xef, 0xf5,
          0xc1, 0xe2, 0xd7, 0xb7, 0x6b, 0x9a, 0xf2, 0x52, 0x45, 0x95, 0xce,
          0x63, 0x65, 0x6b, 0x58, 0x3a, 0xfe, 0xef, 0x7c, 0xe7, 0xbf, 0xfe,
          0x3d, 0xf6, 0x5c, 0x7d, 0x6c, 0x5e, 0x06, 0x09, 0x1a, 0xf5, 0x61,
          0xbb, 0x20, 0x93, 0x09, 0x5f, 0x05, 0x6d, 0xea, 0x87 ],
                      # modulus
    "d": [ 0x87, 0xa7, 0x19, 0x32, 0xda, 0x11, 0x87, 0x55, 0x58, 0x00, 0x16,
          0x16, 0x25, 0x65, 0x68, 0xf8, 0x24, 0x3e, 0xe6, 0xfa, 0xe9, 0x67,
          0x49, 0x94, 0xcf, 0x92, 0xcc, 0x33, 0x99, 0xe8, 0x08, 0x60, 0x17,
          0x9a, 0x12, 0x9f, 0x24, 0xdd, 0xb1, 0x24, 0x99, 0xc7, 0x3a, 0xb8,
          0x0a, 0x7b, 0x0d, 0xdd, 0x35, 0x07, 0x79, 0x17, 0x0b, 0x51, 0x9b,
          0xb3, 0xc7, 0x10, 0x01, 0x13, 0xe7, 0x3f, 0xf3, 0x5f ],
                      # private exponent
    "e": [ 0x5b, 0x7b, 0x88, 0xc0 ] # public exponent
}


req1 = b'\x03\x00\x00\x24\x1F\xE0\x00\x00\x00\x00\x00\x43\x6F\x6F\x6B\x69\x65\x3A\x20\x6D\x73\x74\x73\x68\x61\x73\x68\x3D\x6E\x65\x73\x73\x75\x73\x0D\x0A'

req2 = b'\x03\x00\x01\x96\x02\xF0\x80\x7F\x65\x82\x01\x8A\x04\x01\x01\x04\x01\x01\x01\x01\xFF\x30\x20\x02\x02\x00\x22\x02\x02\x00\x02\x02\x02\x00\x00\x02\x02\x00\x01\x02\x02\x00\x00\x02\x02\x00\x01\x02\x02\xFF\xFF\x02\x02\x00\x02\x30\x20\x02\x02\x00\x01\x02\x02\x00\x01\x02\x02\x00\x01\x02\x02\x00\x01\x02\x02\x00\x00\x02\x02\x00\x01\x02\x02\x04\x20\x02\x02\x00\x02\x30\x20\x02\x02\xFF\xFF\x02\x02\xFC\x17\x02\x02\xFF\xFF\x02\x02\x00\x01\x02\x02\x00\x00\x02\x02\x00\x01\x02\x02\xFF\xFF\x02\x02\x00\x02\x04\x82\x01\x17\x00\x05\x00\x14\x7C\x00\x01\x81\x0E\x00\x08\x00\x10\x00\x01\xC0\x00\x44\x75\x63\x61\x81\x00\x01\xC0\xD4\x00\x04\x00\x08\x00\x00\x04\x00\x03\x01\xCA\x03\xAA\x09\x04\x00\x00\x28\x0A\x00\x00\x74\x00\x65\x00\x6e\x00\x61\x00\x62\x00\x6C\x00\x65\x00\x73\x00\x65\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x0C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xCA\x01\x00\x00\x00\x00\x00\x08\x00\x07\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\xC0\x0C\x00\x09\x00\x00\x00\x00\x00\x00\x00\x02\xC0\x0C\x00\x1B\x00\x00\x00\x00\x00\x00\x00\x03\xC0\x14\x00\x01\x00\x00\x00\x63\x6C\x69\x70\x72\x64\x72\x00\xC0\xA0\x00\x00'

#Taken from https://github.com/SySS-Research/Seth/blob/master/seth/crypto.py
def sign_certificate(cert,slength):
    """Signs the certificate with the private key"""
    m = hashlib.md5()
    m.update(cert)
    m = m.digest() + b"\x00" + b"\xff"*45 + b"\x01"
    m = int.from_bytes(m, "little")
    d = int.from_bytes(TERM_PRIV_KEY["d"], "little")
    n = int.from_bytes(TERM_PRIV_KEY["n"], "little")
    s = pow(m, d, n)
    return s.to_bytes(slength, "little")



s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host_port = sys.argv[1].split(':')
host = host_port[0]
port = int(host_port[1])

s.connect((host, port))

s.sendall(req1)
s.recv(11)

s.sendall(req2)
blob = s.recv(4096)

#Taken from https://github.com/SySS-Research/Seth/blob/master/seth/parsing.py
def substr(s, offset, count):
    return s[offset:offset+count]

def extract_server_cert(bytes):
    # Reference: [MS-RDPBCGR].pdf from 2010, v20100305
    m2 = re.match(b".*010c.*030c.*020c", binascii.hexlify(bytes))
    offset = len(m2.group())//2
    size = struct.unpack('<H', substr(bytes, offset, 2))[0]
    encryption_method, encryption_level, server_random_len, server_cert_len = (
        struct.unpack('<IIII', substr(bytes, offset+2, 16))
    )
    server_random = substr(bytes, offset+18, server_random_len)
    server_cert = substr(bytes, offset+18+server_random_len,
                         server_cert_len)

    #  cert_version = struct.unpack('<I', server_cert[:4])[0]
        # 1 = Proprietary
        # 2 = x509
        # TODO ignore right most bit

    dwVersion, dwSigAlg, dwKeyAlg = struct.unpack('<III',
                                                  substr(server_cert, 0, 12))

    pubkey_type, pubkey_len = struct.unpack('<HH', substr(server_cert, 12, 4))
    pubkey = substr(server_cert, 16, pubkey_len)
    assert pubkey[:4] == b"RSA1"

    sign_type = struct.unpack('<H', substr(server_cert, 16+pubkey_len, 2))[0]
    sign_len = struct.unpack('<H', substr(server_cert, 18+pubkey_len, 2))[0]
    sign = substr(server_cert, 20+pubkey_len, sign_len)

    key_len, bit_len = struct.unpack('<II', substr(pubkey, 4, 8))
    assert bit_len == key_len * 8 - 64
    data_len, pub_exp = struct.unpack('<II', substr(pubkey, 12, 8))
    modulus = substr(pubkey, 20, key_len)

    first5fields = struct.pack("<IIIHH",
                    dwVersion,
                    dwSigAlg,
                    dwKeyAlg,
                    pubkey_type,
                    pubkey_len )
    crypto = {"modulus": modulus,
             "pub_exponent": pub_exp,
             "data_len": data_len,
             "server_rand": server_random, # little endian
             "sign": sign,
             "first5fields": first5fields,
             "pubkey_blob": pubkey,
             "client_rand": b"",
    }
    crypto["pubkey"] = {
        "modulus": int.from_bytes(modulus, "little"),
        "publicExponent": pub_exp,
    }

    return {"crypto": crypto}

encoded_blob = binascii.hexlify(blob)

sig_hex = encoded_blob[-16+-128:-16]

def md5hash(inp):
    m = hashlib.md5()
    m.update(inp)
    return m.hexdigest()

print("\nCertificate signature sent by server:\n")
print(textwrap.indent(textwrap.fill(sig_hex.decode(),32),'\t'))

print()
crypto = extract_server_cert(blob)['crypto']
resigned = sign_certificate(crypto["first5fields"] + crypto["pubkey_blob"],len(crypto["sign"]))
hex_resigned = binascii.hexlify(resigned)
print("[+]Attempting to sign certificate with hardcoded RSA key and produce matching signature...")
print()
print("Certificate signature generated using hardcoded RSA private key:\n")
resigned = hex_resigned[:len(sig_hex)]
print(textwrap.indent(textwrap.fill(resigned.decode(),32),'\t'))
print()
print("MD5 hashes of signatures:\n")
print("Original signature md5 hash: "+md5hash(sig_hex))
print("Hardcoded signed hash:       "+md5hash(resigned))

if hex_resigned[:len(sig_hex)] == sig_hex:
  print()
  print("VULNERABLE: Signatures match, certificate was able to be signed with the hardcoded RSA key.")

else:
  print("Not vulnerable.")
